from Tkinter import *

class DASETK:
  """The following interesting widgets are defined :
    esda.timeline
    esda.scale
    esda.streamsList
    esda.commandLine"""
  def __init__(self):
    self.root = Tk()
    
    topFrame  = Frame(self.root)
    topFrame.pack()
    
    self.initStreamsList(topFrame)
    
    self.timeline = Timeline(topFrame, height=500, width=900)
    self.timeline.pack(side=TOP)
    
    self.scrollbar = Scrollbar(topFrame, orient=HORIZONTAL)
    self.scrollbar.pack(side=TOP, fill=X)
    self.timeline["xscrollcommand"] = self.scrollbar.set
    # self.scrollbar["command"] = self.timeline.xview
    
    self.initCommandLine(self.root)
  
  def __del__(self):
    global connection
    connection.close()
  
  def initStreamsList(self, frame):
    streamsListFrame = LabelFrame(frame, text="Streams", padx=5, pady=5, relief=RIDGE)
    streamsListFrame.pack(side=LEFT)
    self.streamsList = Listbox(streamsListFrame, selectmode=MULTIPLE, height=0)
    self.streamsList.pack()
    
    Button(streamsListFrame, text="Render Selected", command=self.render).pack()
    Button(streamsListFrame, text="Render All", command=self.renderAll).pack()
    
    self.scale = Scale(streamsListFrame, from_=1, to=1, orient=HORIZONTAL, command=renderCallback, showvalue=False)
    self.scale.pack()
    
    self.multiscaleLabel = StringVar()
    Label(streamsListFrame, textvariable=self.multiscaleLabel).pack()
    self.multiscaleLabel.set("...")
    
  
  def initCommandLine(self, frame):
    commandLineFrame = Frame(frame)
    commandLineFrame.pack()
    
    commandLineFrameTop = Frame(commandLineFrame)
    commandLineFrameTop.pack()
    
    Label(commandLineFrameTop, text="> ").pack(side=LEFT)
    self.commandLine  = Entry(commandLineFrameTop, width=80)
    
    self.commandLine.bind("<Return>", runCommand)
    
    self.commandLine.pack(side=LEFT)
    Button(commandLineFrameTop, text="→", command=runCommand).pack()
    
    self.status = StringVar()
    Label(commandLineFrame, textvariable=self.status).pack()
    self.setStatus("Enter commands in the line above.")
  
  def setStatus(self, text):
    self.status.set(text)
  
  def run(self):
    self.root.mainloop()
  
  def setStreamColors(self, stream):
    crc32 = binascii.crc32(stream.name)
    stream.color = "#%02x%02x%02x"%(crc32 % 256, (crc32 >> 8) % 256, (crc32 >> 16) % 256)
    stream.highlightColor = "#%02x%02x%02x"%(
      min((crc32 % 256) + 50, 255),
      min(((crc32 >> 8) % 256) + 50, 255),
      min(((crc32 >> 16) % 256) + 50, 255))
  
  def renderAll(self):
    global show
    show((0, END))
  
  def render(self):
    self.timeline.render()

  
  def init():
    """init() initialises the application (useful for testing)"""
    
    print "Initialisation...",
    
    global streams, connection
    
    try:
      connection.close()
      connection = None
    except NameError:
      pass
    
    connection = apsw.Connection(":memory:")
    
    if globals().has_key('streams'):
      print "Remove existing streams...",
      streams.clear()
    
    streams = StreamDict()
    
    esda.timeline.render()
    
    print "Done."
  
  def render():
    profilePrerendering = time.clock()
    streamsToDisplay   = []
    streamIdsToDisplay = [int(id) for id in esda.streamsList.curselection()];
    
    esda.timeline.delete(ALL)
    if not len(streamIdsToDisplay):
      return
    
    timeStart = None
    timeEnd   = None
    timeScale = None
    
    for streamId in streamIdsToDisplay:
      streamsToDisplay.append(streams[streamId])
      
      streamFirstStart = streams[streamId].getFirstStart()
      if streamFirstStart != None and (timeStart == None or streamFirstStart < timeStart):
        timeStart = streamFirstStart
      
      streamLastEnd = streams[streamId].getLastEnd()
      if streamLastEnd != None and (timeEnd == None or streamLastEnd > timeEnd):
        timeEnd = streamLastEnd
      
      streamMinEventLength = streams[streamId].getMinEventLength()
      if streamMinEventLength > 0 and (timeScale == None or streamMinEventLength < timeScale):
        timeScale = streamMinEventLength

    
    if timeScale == None:
      timeScale = 1
    
    esda.timeline.zoom = esda.scale.get()
    timeLength = float(timeEnd - timeStart)
    
    facteur = 1.05
    
    esda.scale["from"]       = math.log(float(esda.timeline["width"]) / timeLength, facteur)
    esda.scale["to"]         = math.log(30. / timeScale, facteur)
    esda.scale["resolution"] = float(esda.scale["to"] - esda.scale["from"]) / 100.
    
    esda.scale.set(esda.timeline.zoom)
    esda.timeline.zoom = facteur**float(esda.scale.get())
    
    esda.timeline["scrollregion"] = (0, 0, int(timeLength * esda.timeline.zoom), int(esda.timeline["height"]))
    
    displayHalfTimeLength = int(esda.timeline["width"]) / (2. * esda.timeline.zoom)
    esda.timeline.xview_moveto((esda.timeline.focusTime - displayHalfTimeLength) / timeLength)
    
    displayStartTime    = int(esda.timeline.focusTime - displayHalfTimeLength)
    displayEndTime    = displayStartTime + displayHalfTimeLength * 2
    
    if displayStartTime < 0:
      displayStartTime = 0
      displayEndTime   = 2 * displayHalfTimeLength
    
    if displayEndTime > timeLength:
      displayEndTime   = timeLength
      displayStartTime = displayEndTime - 2 * displayHalfTimeLength
    
    displayStartTime += timeStart
    displayEndTime   += timeStart
    
    intervalle = datetime.datetime.fromtimestamp(displayEndTime) - datetime.datetime.fromtimestamp(displayStartTime)
    
    calendarStartTime = None
    displayStartTimeAsDateTime = datetime.datetime.fromtimestamp(displayStartTime)
    
    if intervalle.days > 0:
      calendarStartTime = datetime.datetime(
        displayStartTimeAsDateTime.year,
        displayStartTimeAsDateTime.month,
        1)
      calendarTimes = range(0, 30)
      calendarScale = 86400
      couleurs = ["#CCFF99", "#BBCC99"]
      esda.multiscaleLabel.set("Highlight Days")
    elif intervalle.seconds > 3600:
      calendarStartTime = datetime.datetime(
        displayStartTimeAsDateTime.year,
        displayStartTimeAsDateTime.month,
        displayStartTimeAsDateTime.day)
      calendarTimes = range(0, 24)
      calendarScale = 3600
      couleurs = ["#99FFCC", "#88BB99"]
      esda.multiscaleLabel.set("Highlight Hours")
    else:
      calendarStartTime = datetime.datetime(
        displayStartTimeAsDateTime.year,
        displayStartTimeAsDateTime.month,
        displayStartTimeAsDateTime.day,
        displayStartTimeAsDateTime.hour)
      calendarTimes = range(0, 60)
      calendarScale = 60
      couleurs = ["#AAEEFF", "#99BBBB"]
      esda.multiscaleLabel.set("Highlight Minutes")
    
    calendarStartTime = time.mktime(calendarStartTime.timetuple()) - timeStart
    calendarEndTime = displayEndTime + calendarScale - timeStart
    height = int(esda.timeline["height"])
    
    calendarCurrentTime = calendarStartTime
    
    while calendarCurrentTime < calendarEndTime:
      esda.timeline.create_rectangle(
        int(calendarCurrentTime * esda.timeline.zoom),
        0,
        int((calendarCurrentTime + calendarScale) * esda.timeline.zoom),
        height,
        tags='pair')
      
      calendarCurrentTime += calendarScale
      
      esda.timeline.create_rectangle(
        int(calendarCurrentTime * esda.timeline.zoom),
        0,
        int((calendarCurrentTime + calendarScale) * esda.timeline.zoom),
        height,
        tags='impair')
      
      calendarCurrentTime += calendarScale
    
    esda.timeline.itemconfig(ALL, outline="")
    esda.timeline.itemconfig("pair", fill=couleurs[0])
    esda.timeline.itemconfig("impair", fill=couleurs[1])
    
    height   = float(esda.timeline["height"]) / float(len(streamsToDisplay))
    position = -1
    
    print "Prerender :", time.clock() - profilePrerendering, "s",
    
    profileStreamRendering = time.clock()
    
    for stream in streamsToDisplay:
      position += 1
      
      maxValue      = stream.getMaximumValue()
      minValue      = min(stream.getMinimumValue(), 0)
      intervalValue = float(maxValue - minValue)
      
      if intervalValue == 0:
        # All values in the stream are necessarily 0
        # Hence any values here, as long as they are not incoherent, will
        # result in no display ; which is what we want
        intervalValue = 1
        minValue      = 0
        maxValue      = 1
      
      streamTag = "Stream: %s"%(stream.name)
      
      for event in stream[displayStartTime:displayEndTime]:
        left = int((event[1] - timeStart) * esda.timeline.zoom)
        
        esda.timeline.create_rectangle(
          left,
          height * (position + 1. + (minValue - event[3]) / intervalValue),
          max(left + 2, int((event[2] - timeStart) * esda.timeline.zoom)),
          height * (position + 1. + minValue / intervalValue),
          tags=(streamTag, "Event: %s %s"%(stream.name, event[0])))
      
      esda.timeline.itemconfig(
        streamTag,
        fill      = stream.color,
        outline     = stream.highlightColor)
      
      esda.timeline.create_text(
        10 + (displayStartTime - timeStart) * esda.timeline.zoom,
         5 + height * position,
        anchor="nw", text=stream.name, font=("Times", 14, "bold"))
    
    print "Streams :", time.clock() - profileStreamRendering, "s",
    
    activeEventTag = "Event: %s %s"%esda.timeline.activeEvent
    
    esda.timeline.itemconfig(activeEventTag, fill="#FF0000")
    esda.timeline.tag_raise(activeEventTag)
    
    esda.timeline.create_line(
      esda.timeline.focusTime * esda.timeline.zoom,
      0,
      esda.timeline.focusTime * esda.timeline.zoom,
      esda.timeline["height"],
      fill="red");
  
  
  def renderCallback(arg):
    esda.timeline.render()
  
  def show(streamId):
    """show(streamId) selects stream streamId for rendering"""
    if isinstance(streamId, tuple):
      esda.streamsList.selection_set(streamId[0], streamId[1])
    else:
      esda.streamsList.selection_set(streamId)
    
    esda.timeline.render()
  
  def hide(streamId):
    """hide(streamId) unselects stream streamId for rendering"""
    if isinstance(streamId, tuple):
      esda.streamsList.selection_clear(streamId[0], streamId[1])
    else:
      esda.streamsList.selection_clear(streamId)
    
    esda.timeline.render()
  
  def printValue(value):
    """printValue(streamValue) prints a value returned by a stream (which is generally a SQLite Cursor Object)"""
    
    for event in value:
      print event;
  
  def help():
    """HELP for ESDA
      ----
      """
    
    print help.__doc__
    print ESDA.__doc__
    print newStream.__doc__
    print init.__doc__
  
  def runCommand(event = None):
    global esda
    command = esda.commandLine.get()
    
    esda.setStatus("Running : " + command)
    
    try:
      # We try to evaluate the command as an expression
      val = eval(command)
      
      if val != None:
        print val
      
      esda.setStatus("Expression evaluated successfully")
    except:
      try:
        # We failed evaluating it, so we execute it as a command
        exec command
        esda.setStatus("Command run successfully")
      except:
        raise